package com.ease.framework {

import com.ease.util.Diagnostics;
import com.ease.util.EventTypeUpdateEvent;
import com.ease.util.MixedUtil;
import com.ease.util.ReflectionUtil;
import com.ease.util.Set;
import com.ease.util.StringUtils;
import com.ease.util.TaggedMemberInfo;
import com.ease.util.TaggedMethodInfo;
import com.ease.util.TaggedPropertyInfo;
import com.ease.util.events.ErrorEvent;
import com.ease.util.logging.LogManager;
import com.ease.framework.priv.ResponseInfo;

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.Dictionary;

import mx.core.FlexGlobals;
import mx.events.FlexEvent;
import mx.logging.ILogger;
import mx.managers.SystemManager;

/**
 * EventBus notifies all errors found on the response events that pass through this bus.
 * Other platform services can also notify their errors by dispatching ErrorEvent on this
 * bus.
 */
[Event(name="errorOccurred", type="com.ease.util.events.ErrorEvent")]
/**
 * This class provides a subscriber/publisher style event messaging model. The message
 * participants (subscribers and publishers) don't know about one another directly and
 * the subscription and publishing is based on event IDs (i.e., the <code>type</code>
 * property of a flex <code>Event</code>).
 *
 * <p>The event publishers declare themselves as the publishers of events by way of the
 * standard flex [Event] tag. The event subscribers delcare themselves as the sink or
 * handler of events by use of the [EventHandler] or the [ResponseHandler] tag on their
 * listener methods.
 *
 * <p>An [EventHandler] method takes the event as its only arg. Its intended use is for
 * receiving general app-wide notifications.
 *
 * <p>A [ResponseHandler] method assumes that the event is a "response" of some sort, and
 * expects to receive either the response event itself, or between 2 to 4 of these
 * arguments: the properties of the response event that are labeled as [Request],
 * [Result], [Error] and [ResultInfo]. The error and result-info properties are optional,
 * i.e., the handler method can have just the first two args: request and result.
 *
 * <p>[ResponseHandler] can be used on a property or var as well, in which case, only the
 * [Result] part of the response event is delivered to the listener. This gives a more
 * elegant class design, assuming errors in the responses are handled in some other way
 * (e.g., using [ErrorHandler] as described below, or yet better, in an aspect-oriented
 * manner by having a platform level error handler).
 *
 * <p>An event is considered a "request" event if its type declaration includes a
 * [Response] tag at the class level (unless the event is a "response" to an earlier
 * request). Whenever a request event is received by this bus, it re-registers the
 * dispatcher using a strong-reference until a corresponding response event is received
 * (so that the client doesn't need to worry about dispatcher getting garbage collected
 * before it has had a chance to process the response). The framework assumes that every
 * request event is followed by a response event at some point.
 *
 * <p>An event is considered a "response" event if it has a [Request] tag that references
 * a known request, or if it has a [RequestSource] tag that references a known dispatcher.
 *
 * <p>If an Event or EventHandler tag is not intended to be processed by the event bus,
 * the tag can specify scope="private" to opt out of the global registration. This way,
 * the notification of events dispatched by the object, or the ability to invoke its event
 * hanlders is available only to those who have a direct reference to the object.
 *
 * <p>In order for an event to be dispatched correctly through the EventBus, its class
 * should define a clone method. Overriding clone() in custom event definitions is a flex
 * best-practice, and necessary for a proper event redispatch since events get cloned
 * when being redispatched.
 *
 * <listing version="3.0">
 * [Response(name="{RESPONSE_ID}", type="com.some.ns.events.SomeResponseEvent")]
 * public class SomeRequestEvent extends Event
 * {
 *    public static const REQUEST_ID:String = "someRequest";
 *    public static const RESPONSE_ID:String = "someResponse";
 *    // ...
 *    public var whatever:SomeDataType;
 *    // ...
 *    public override function clone():Event {
 *       return new SomeRequestEvent( type, whatever );
 *    }
 * }
 * </listing>
 *
 * <p>Multiple request-response id pairs could also be specified on a single event type
 * as shown in the following example:
 *
 * <listing version="3.0">
 * [Response(name="{RESPONSE1_ID}", type="...Response1Event", request="{REQUEST1_ID}")]
 * [Response(name="{RESPONSE2_ID}", type="...Response2Event", request="{REQUEST2_ID}")]
 * public class SomeRequestEvent extends Event
 * {
 *    public static const REQUEST1_ID:String = "someRequest1";
 *    public static const RESPONSE1_ID:String = "someResponse1";
 *    public static const REQUEST2_ID:String = "someRequest2";
 *    public static const RESPONSE2_ID:String = "someResponse2";
 *    // ...
 * }
 * </listing>
 *
 * <p>If the type parameter is skipped in the Response tag, the request event class
 * itself is used as the response type. If the class has a property marked as
 * [RequestSource], the framework sets this property with the <code>target</code> (i.e.,
 * the sender) of the original request event when creating the response.
 *
 * <p>[ResponseHandler] style allows the handler of a given event to be type-specific,
 * even if the said event's [Result] or [Error] properties are not type-specific. This is
 * useful when the result type is somehow known to the consumer of an event, even though
 * the type is not declared specifically in the event class.
 *
 * <p>An EventHandler or ResponseHandler declaration (on some method of a class whose
 * instances are being handled by this bus) implies a subscription not just based on the
 * event id, but also the types of its arguments (e.g., the event class as declared by the
 * method's first argument, which in case of EventHandler is the only argument, or the
 * return type in case of a property). In other words, if an event is raised on the bus
 * such that the event type is not compatible with the argument of an event handler
 * method, it will not cause a TypeError; a message will however be logged. (Note that
 * this behavior is enabled only if the object was registered without weak-referencing;
 * such type-checking cannot be provided otherwise.) Ideally however, a unique or
 * qualified naming scheme should be employed if the events are to be used for global
 * messaging (as opposed to local or private messaging).
 *
 * <p>Apart from event registration through declarations as described above, a client can
 * also dynamically register for new event types (as a source or sink of those events) by
 * dispatching an <code>EventTypeUpdateEvent</code> for the system at large (e.g., by
 * dispatching it on the gloabl Application object).
 *
 * <p>For an event handler (declared either through EventHandler, or ResponseHandler tag)
 * that subscribes for events that are responses to some request events, the default
 * behavior is for the event handler to be invoked only if the corresponding request event
 * was dispatched by the owner/target of the listener. This default behavior can be
 * overriden by specifying scope="promiscuous" in the tag, such that an event of the said
 * response type would be delivered to the handler regardless of which object raised the
 * corresponding request event.
 *
 * <p>If and when a listener is weak-referenced, the only available mode is "promiscuous".
 * [ResponseHandler]s are ignored for such listeners, and [EventHandler]s behave
 * promiscuously.
 *
 * <p>If an event contains an error and the target of the event notification has a generic
 * error handler (i.e., a method that is tagged as [ErrorHandler]), then that method is
 * used to report the error. In such a case, if the event/response handler takes only 2
 * parameters, i.e., does not take error as a parameter, the event/response handler is
 * skipped. In other words, if an event contains an error and the target has not
 * subscribed for the error parameter, then partial results are ignored.
 *
 * <p>If any property on the requesting object (i.e., sender of the request) is marked
 * with the following, and the value of such a property changes between the time the
 * request is made and the response is received by the framework, the framework drops the
 * response event (i.e., the event is not delivered to the requestor):
 *
 * <listing version="3.0">
 * [Context(changeInvalidatesRequests = "true")]
 * public var someProperty:SomePropertyType;
 * </listing>
 *
 * <p>Note that this behavior applies only if the event handler is not marked as
 * "promiscuous" and the response event contains a reference to the respective request
 * event (using a property tagged as [Request]).
 *
 * <p>Following is an example of an event dispatcher and consumer. Note that the class
 * needs to be an IEventDispatcher only if it is also a source of events, not just a
 * consumer.
 *
 * <listing version="3.0">
 * [Event(name="{some.ns::SomeEvent.SOME_ID}", type="some.ns.SomeEvent")]
 * [Event(name="someOtherEvent", type="some.other.ns.SomeOtherEvent")]
 * public class SomeClass extends EventDispatcher
 * {
 *    // ...
 *    private function foo( something:T1 ):T2 {
 *       // ...
 *       dispatchEvent( new SomeEvent( SomeEvent.SOME_ID, whatever1 ) );
 *       // ...
 *    }
 *    // ...
 *    private function bar( something:T3 ):T4 {
 *       // ...
 *       dispatchEvent( new SomeOtherEvent( "someOtherEvent", whatever2 ) );
 *       // ...
 *    }
 *    // ...
 *    [EventHandler(name="someInterestingEvent")]
 *    public function onSomething( ev:SomeInterestingEvent ):void {
 *       // ...
 *    }
 *    // ...
 *    [ResponseHandler(name="someResponseEvent")]
 *    public function onSomethingElse( request:SomeRequestEvent, result:SomeType,
 *                                     error:SomeError ):void {
 *       // ...
 *    }
 *    // Or:
 *    [ResponseHandler(name="someResponseEvent")]
 *    public function set someData( value:SomeType ):void {
 *       // ...
 *    }
 * }
 * </listing>
 *
 * <p>Note that the class-level [Using] metatag can be used to abbreviate references to
 * namespace anywhere in the frinje tags. For example:
 *
 * <listing version="3.0">
 * [Using( name="some.namespace", alias="ns" )]
 * [Event(name="{ns:SomeEvent.SOME_ID}", ... )]
 * [Event(name="{ns:SomeOtherEvent.SOME_ID}", ... )]
 * public class SomeClass extends EventDispatcher
 * {
 *    // ...
 *    [EventHandler(name="ns:SomeInterestingEvent.SOME_ID")]
 *    public function onSomething( ev:SomeInterestingEvent ):void {
 *       // ...
 *    }
 * }
 * </listing>
 *
 * @see EventTypeUpdateEvent
 */
public class EventBus extends EventDispatcher
{
   private static const _logger:ILogger = LogManager.getLogger( EventBus );
   private static const MODIFIER_SCOPE_PRIVATE:String = "private";
   private static const MODIFIER_SCOPE_PROMISCUOUS:String = "promiscuous";
   private static const TAGPARAM_SCOPE:String = "scope";
   private static const TAG_RESPONSE_HANDLER_KEY:String = "ResponseHandler";
   private static const TAG_EVENT_HANDLER_KEY:String = "EventHandler";
   private static var _instance:EventBus;
   /**
    * Keeps a record of what objects are currently registered on this bus (i.e., were
    * registered through addParticipant and have not been removed through
    * removeParticipant). This information is useful in at least two ways:
    *     (1) for debugging purposes
    * and (2) to provide idempotency of add/removeParticipant without incurring the cost
    * of reflection (on the object) multiple times.
    */
   private var _infoByParticipant:Dictionary/*(Object, ParticipantInfo)*/ =
         new Dictionary( true );
   private var _originalEventsByClone:Dictionary = new Dictionary( true );
   /**
    * Keeps track of the event delegates (or function instances) for the objects that are
    * subscribed to this bus.
    */
   private var _functorsByParticipant:Dictionary = new Dictionary();
   private var _predispatchCallbacks:Set/*Function*/ = new Set();
   private var _requestIdByResponseId:Dictionary = new Dictionary();
   private var _responseIdByRequestId:Dictionary = new Dictionary();

   /**
    * A global instance of this class. This is the instance that would typically be used
    * by an application.
    *
    * @return
    *    An instance of EventBus for global use.
    */
   public static function get instance():EventBus {
      if (_instance == null) {
         _instance = new EventBus();
         _instance.globalInstanceInit();
      }
      return _instance;
   }

   /**
    * Initializes an instance of this class.
    */
   public function EventBus() {
      // Each bus watches its own dominion for dynamic event registrations.
      addEventListener( EventTypeUpdateEvent.TYPE_ADDED_TO_DISPATCHER,
                        onTypeAddedToDispatcher );
      addEventListener( EventTypeUpdateEvent.TYPE_REMOVED_FROM_DISPATCHER,
                        onTypeRemovedFromDispatcher );
   }

   /**
    * Registers the given object to participate in the event based communication. The
    * following must hold true for the object to become event producer:
    * <ol>
    * <li> Contain Event metadata tag on the class definition (specified as <code>
    * [Event(name="someEventId", type="SomeEventType")]</code>), one each for all the
    * events dispatched by this class that are of broadcast interest
    * <li> Implement <code>IEventDispatcher</code>.
    * </ol>
    *
    * <p>The object that is an event consumer must annotate its event handler functions
    * with the <code>[EventHandler("event1","event2",...)]</code> metadata tag.
    *
    * @param participant
    *    The object wishing to participate in the event based messaging.
    *
    * @param useWeakReference
    *    Indicates whether weak referencing should be used for the given object.
    */
   public final function addParticipant( participant:Object,
                                         useWeakReference:Boolean = false ):void {
      if (participant == null) {
         return;
      }
      var info:ParticipantInfo = _infoByParticipant[participant];
      if (info != null && info.isParticipantWeakReferenced == useWeakReference) {
         // the participant is already registered with the same referencing mode
         return;
      }
      if (info != null) {
         // The referencing mode is being modified, so we'll re-register the object.
         unregisterAsSubscriber( participant );
         registerAsSubscriber( participant, useWeakReference );
         info.isParticipantWeakReferenced = useWeakReference;
         return;
      }
      info = new ParticipantInfo();
      info.isParticipantWeakReferenced = useWeakReference;
      _infoByParticipant[participant] = info;
      registerAsPublisher( participant );
      registerAsSubscriber( participant, useWeakReference );
   }

   /**
    * Unregisters the given object from the event bus so that it will no longer send or
    * receive events through this bus.
    *
    * @param participant
    *    The object to be unregistered from the event based communication.
    */
   public final function removeParticipant( participant:Object ):void {
      if (participant == null) {
         return;
      }
      if (_infoByParticipant[participant] == null) {
         return;
      }
      delete _infoByParticipant[participant];
      unregisterAsPublisher( participant );
      unregisterAsSubscriber( participant );

      // TODO mvdb: A delay of some type is need to avoid error in the EventFunctor
      // This needs to be investigated furthor, so for now we won't call removeRequestor
      // A delay is required, or else an active EventFunctor may fail to find response
      // or request info.
      //_pendingRequestRegistry.removeRequestor( participant );
   }

   /**
    * @inheritDoc
    *
    * @internal If the given event represents a response object, it's removed from the
    * associated PendingRequestRegistry once the event has been dispatched.
    */
   public override function dispatchEvent( event:Event ):Boolean {
      preprocessAsRequestIfApplicable( event );
      preprocessAsResponseIfApplicable( event );

      var returnVal:Boolean;
      if (! isValidDispatch( event )) {
         returnVal = false;
      } else {
         returnVal = super.dispatchEvent( event );
      }

      postprocess( event );
      return returnVal;
   }

   //-------------------------------------------------------
   // Property: pendingRequestRegistry

   private var _pendingRequestRegistry:PendingRequestRegistry =
         new PendingRequestRegistry();

   /**
    * @private
    *
    * The pending request registry associated with this event bus, which other framework
    * entities can register requests and responses against, such that this event bus can
    * then validate the responses before dispatching them using the information provided
    * by this request registry.
    */
   internal function get pendingRequestRegistry():PendingRequestRegistry {
      return _pendingRequestRegistry;
   }

   /**
    * @private
    *
    * This method can be used to look up the original event dispatched on the bus, using
    * the bus-redispatched version of that event. If the given event was directly raised
    * on the bus, the event itself is the original, hence the method returns the same
    * event.
    *
    * @param event
    *    An event dispatched by the bus for its event subscribers.
    *
    * @return
    *    The event originally dispatched by an event publisher that is the equivalent of
    *    the given event.
    */
   internal final function originalFromRedispatchedEvent( event:Event ):Event {
      if (event == null) {
         return null;
      }
      var original:Event = _originalEventsByClone[event];
      if (original == null && event.target == this) {
         // The event was raised directly on this bus.

         original = event;
      }
      return original;
   }

   /**
    * Other framework entities can register a callback for notification and filtering
    * purposes, to be invoked whenever an event is to be dispatched on the bus. The
    * callbacks are also allowed to modify properties of the event (e.g., fill-in or
    * transform some properties). The callback is supposed to be of the form:
    *
    * <p><code>function foo( event:Event ):Boolean</code>
    *
    * <p>The boolean returned by the callback is treated as a filtering flag. All
    * callbacks are invoked and if any of them returns false, the event is not dispatched.
    *
    * <p>It's intended for use by other frameworks built on top of frinje, e.g., a logging
    * framework (i.e., it's not recommended for use by the application code).
    */
   public final function addPredispatchCallback( callback:Function ):void {
      _predispatchCallbacks.add( callback );
   }

   /**
    * Removes a previously registered predispatch callback.
    *
    * <p>It's intended for use by other frameworks built on top of frinje, e.g., a logging
    * framework (i.e., it's not recommended for use by the application code).
    */
   public final function removePredispatchCallback( callback:Function ):void {
      _predispatchCallbacks.remove( callback );
   }

   /**
    * Checks whether the given object is a listener to the given event using an
    * <code>EventHandler</code> metadata tag.
    * Note that this function considers only objects added to the bus with strong
    * references.
    */
   public function isEventListener(listener:Object, eventType:String):Boolean {
      var functors:Array = MixedUtil.objectAsCollection(_functorsByParticipant[listener]);
      for each (var functor:EventFunctor in functors) {
         if (functor.handlerInfo.tagValue == eventType) {
            return true;
         }
      }
      return false;
   }

   private function globalInstanceInit():void {
      var app:EventDispatcher = FlexGlobals.topLevelApplication as EventDispatcher;
      // The global event bus not only watches its own dominion for dynamic event
      // registrations, but it also watches the global Application object, so that
      // if an object who doesn't know anyone else who is already on the event bus can
      // also register (by raising events on the SystemManager).
      // Note that we don't (need to) use weak-referencing here (i.e., when adding the
      // listeners) because the global event bus instance is for the lifetime of the
      // app.
      app.addEventListener( EventTypeUpdateEvent.TYPE_ADDED_TO_DISPATCHER,
                            onTypeAddedToDispatcher );
      app.addEventListener( EventTypeUpdateEvent.TYPE_REMOVED_FROM_DISPATCHER,
                            onTypeRemovedFromDispatcher );
   }

   private function registerAsPublisher( participant:Object ):void {
      if (! (participant is IEventDispatcher)) {
         return;
      }
      var eventNames:Array =
            ReflectionUtil.getClassMetadata( participant, "Event", "name" );
      for each (var evName:String in eventNames) {
         var scope:String = ReflectionUtil.getFilteredClassTagValue(
               participant, "Event", "name", evName, TAGPARAM_SCOPE );
         if (scope == MODIFIER_SCOPE_PRIVATE) {
            continue;
         }
         participant.addEventListener( evName, onEvent );
      }
   }

   private function
   registerAsSubscriber( participant:Object, useWeakReference:Boolean ):void {
      // Register event-handler methods.
      var handlerTag:String = TAG_EVENT_HANDLER_KEY;
      var eventHandlerInfos:Array/*TaggedMemberInfo*/ =
            ReflectionUtil.getTaggedMethods( participant, handlerTag, "name" );
      registerListeners( participant, eventHandlerInfos, useWeakReference, handlerTag );

      // Register response-handler methods.
      handlerTag = TAG_RESPONSE_HANDLER_KEY;
      eventHandlerInfos =
            ReflectionUtil.getTaggedMethods( participant, handlerTag, "name" );
      registerListeners( participant, eventHandlerInfos, useWeakReference, handlerTag );

      // Register response-handler properties.
      handlerTag = TAG_RESPONSE_HANDLER_KEY;
      eventHandlerInfos =
            ReflectionUtil.getTaggedProperties( participant, handlerTag, "name" );
      registerListeners( participant, eventHandlerInfos, useWeakReference, handlerTag );
   }

   private function unregisterAsPublisher( participant:Object ):void {
      if (!(participant is IEventDispatcher)) {
         return;
      }
      var eventNames:Array =
            ReflectionUtil.getClassMetadata( participant, "Event", "name" );
      for each(var evName:String in eventNames) {
         participant.removeEventListener( evName, onEvent );
      }
   }

   private function unregisterAsSubscriber( participant:Object ):void {
      if (_functorsByParticipant[participant] != null) {
         removeFunctorsForParticipant( participant );
      } else {
         removeParticipantListenersFromBus( participant );
      }
   }

   private function
   registerListeners( participant:Object, eventHandlerInfos:Array,
                      useWeakReference:Boolean, handlerTag:String ):void {
      var evName:String;
      var scope:String;
      for each (var evHandlerInfo:TaggedMemberInfo in eventHandlerInfos) {
         var memberName:String = evHandlerInfo.name;
         evName = evHandlerInfo.tagValue as String;
         scope = ReflectionUtil.getFilteredMethodTagValue(
               participant, memberName, handlerTag, "name", evName, TAGPARAM_SCOPE );
         if (scope == MODIFIER_SCOPE_PRIVATE) {
            continue;
         }
         if (useWeakReference) {
            if (! isWeakSubscriptionOk( handlerTag, scope )) {
               _logger.info( "Handler registration skipped due to weak ref: " +
                             memberName );
               continue;
            }
            var listener:Function = participant[memberName];
            addEventListener( evName, listener, false, 0, useWeakReference );
         } else {
            // For the strong reference cases, we have the cability to check for
            // compatibility between type of the raised event and the event type that the
            // listener is expecting. Hence the difference between the way weak
            // referenced participants are addressed, compared to the strong referenced
            // ones.
            addFunctorForParticipant( evHandlerInfo, participant, handlerTag, scope );
         }
      }
   }

   private static function isWeakSubscriptionOk( handlerTag:String,
                                                 scope:String ):Boolean {
      if (handlerTag == TAG_RESPONSE_HANDLER_KEY) {
         // Can't register a response-handler with weak-referencing because response
         // handlers require re-direction through EventFunctor`s, and functors need
         // strong referencing.
         return false;
      }
      return true;
   }

   private function
   addFunctorForParticipant( evHandlerInfo:TaggedMemberInfo, participant:Object,
                             handlerTag:String, scope:String ):void {
      var listener:Function;
      if (evHandlerInfo is TaggedPropertyInfo) {
         if (TaggedPropertyInfo( evHandlerInfo ).access ==
             ReflectionUtil.ACCESS_READONLY) {
            Diagnostics.assert( false, "Response handler property can't be readonly!" );
            return;
         }
      } else {
         listener = participant[evHandlerInfo.name];
      }
      var functor:EventFunctor = new EventFunctor();
      functor.target = participant;
      functor.listener = listener;
      functor.handlerInfo = evHandlerInfo;
      functor.isResponseHandler = handlerTag == TAG_RESPONSE_HANDLER_KEY;
      functor.isReflexiveOnly = scope != MODIFIER_SCOPE_PROMISCUOUS;
      functor.pendingRequestRegistry = _pendingRequestRegistry;
      addEventListener( evHandlerInfo.tagValue as String, functor.onEvent );

      var currentFunctorVal:Object = _functorsByParticipant[participant];
      if (currentFunctorVal == null) {
         _functorsByParticipant[participant] = functor;
      } else if (currentFunctorVal is Array) {
         (currentFunctorVal as Array).push( functor );
      } else {
         _functorsByParticipant[participant] = [currentFunctorVal, functor];
      }
   }

   /**
    * Deals with only direct listeners, not EventFunctor`s. Hence it doesn't need to
    * worry about [ResponseHandler]s as they're always wrapped inside functors.
    */
   private function removeParticipantListenersFromBus( participant:Object ):void {
      // Only need to process "EventHandler" tag and not "ResponseHandler" because direct
      // subscription (i.e., without an EventFunctor) is only for event-handlers.
      var eventHandlerInfos:Array =
            ReflectionUtil.getTaggedMethods( participant, TAG_EVENT_HANDLER_KEY );
      for each (var evHandlerInfo:TaggedMethodInfo in eventHandlerInfos) {
         removeEventListener( evHandlerInfo.tagValue as String,
                              participant[evHandlerInfo.name] );
      }
   }

   private function removeFunctorsForParticipant( participant:Object ):void {
      var functorVal:Object = _functorsByParticipant[participant];
      if (functorVal == null) {
         return;
      }
      delete _functorsByParticipant[participant];
      var functor:EventFunctor;
      if (functorVal is EventFunctor) {
         functor = EventFunctor( functorVal );
         removeEventListener( functor.handlerInfo.tagValue as String, functor.onEvent );
         return;
      }
      if (functorVal is Array) {
         for each (functor in (functorVal as Array)) {
            removeEventListener( functor.handlerInfo.tagValue as String,
                                 functor.onEvent );
         }
      }
   }

   /**
    * The glue that informs all the registered event handlers about the event triggered
    * by a participating dispatcher.
    */
   private function onEvent( event:Event ):void {
      var evClone:Event = event.clone();
      _originalEventsByClone[evClone] = event;
      dispatchEvent( evClone );
   }

   /**
    * Used to pre-process an event (if it's a request) just before dispatching it (on this
    * bus).
    */
   private function preprocessAsRequestIfApplicable( event:Event ):void {
      if (! isRequestEvent( event )) {
         return;
      }
      var originalEvent:Event = originalFromRedispatchedEvent( event );
      var requestor:Object = originalEvent.target;
      _pendingRequestRegistry.addRequest( originalEvent, requestor );

      // Here we ensure that the dispatcher is registered with strong-referencing, so that
      // it'll stick around till the respnose is received.
      var info:ParticipantInfo = _infoByParticipant[requestor];
      if (info == null) {
         Diagnostics.assert(
               false, "Dispatcher of the event must be registered with the bus!" );
         return;
      }
      info.pendingRequestCount++;
      if (info.isParticipantWeakReferenced == false) {
         // the dispatcher is already registered with strong-referencing
         return;
      }
      // The referencing mode is to be modified, so re-register the object. But do NOT
      // alter the flag in ParticipantInfo, since this registration is temp and will be
      // undone when we receive the corresponding response.
      unregisterAsSubscriber( requestor );
      registerAsSubscriber( requestor, false );
   }

   /**
    * Used to pre-process an event (if it's a response) just before dispatching it (on
    * this bus).
    */
   private function preprocessAsResponseIfApplicable( event:Event ):void {
      if (! isResponseEvent( event )) {
         return;
      }
      var responseInfo:ResponseInfo = ResponseInfo.fromResponse( event );
      var request:Event = responseInfo.request as Event;

      if (request != null) {
         var originalRequest:Event = originalFromRedispatchedEvent( request );
         if (originalRequest == null) {
            originalRequest = request;
         } else {
            // Overwrite the request info contained in the response event with the
            // "original" version of that info (since redispatch of the request event
            // must have caused cloning of the event and changed the target).
            ResponseInfo.setRequestOnResponse( originalRequest, event, responseInfo );
         }
      }
      // (Re)register the response with the pending request registry.
      _pendingRequestRegistry.addResponse( event, responseInfo );
   }

   private function restoreWeaknessOfRequestorIfApplicable( requestor:Object ):void {
      var info:ParticipantInfo = _infoByParticipant[requestor];
      if (info == null) {
         // The requestor might have been removed from frinje (or at least this event bus)
         // while a response was pending on the request. In that case, there's nothing for
         // us to do.
         return;
      }
      info.pendingRequestCount--;
      if (info.pendingRequestCount == 0 && info.isParticipantWeakReferenced) {
         // Here we undo the temporary strong-registration that this bus might have done
         // on the requestor object.
         unregisterAsSubscriber( requestor );
         registerAsSubscriber( requestor, true );
      }
   }

   private function postprocess( event:Event ):void {
      notifyContainedErrorIfApplicable( event );
      var responseInfo:ResponseInfo = _pendingRequestRegistry.getResponseInfo( event );
      if (responseInfo == null) {
         return;
      }

      // See if something needs to be done to the requestor of this response.
      var requestor:Object = responseInfo.requestor;
      if (requestor == null) {
         Diagnostics.assert( false,
               "Either [Request] or [RequestSource] property must be set on a response." );
      } else {
         restoreWeaknessOfRequestorIfApplicable( requestor );
      }

      _pendingRequestRegistry.removeResponse( event );
   }

   private function notifyContainedErrorIfApplicable( event:Event ):void {
      if (event.type == ErrorEvent.REQUEST_ID) {
         // To avoid pathological recursion. Error notifying events are not supposed to
         // generate new errors.
         return;
      }
      var responseInfo:ResponseInfo = _pendingRequestRegistry.getResponseInfo( event );
      if (responseInfo == null) {
         // Currently we notify on response errors only.
         return;
      }
      if (responseInfo.error == null) {
         return;
      }
      var errEvent:ErrorEvent =
            new ErrorEvent( responseInfo.error, event, responseInfo.requestor );
      if (responseInfo.wasErrorDelivered) {
         errEvent.errorStatus |= ErrorEvent.ERROR_DELIVERED;
      }
      // Dispatch through base class to avoid reflection and other processing that this
      // class does as that's unnecessary for "self" events.
      super.dispatchEvent( errEvent );
   }

   private function isRequestEvent( event:Event ):Boolean {
      var responseId:String = _responseIdByRequestId[event.type];
      if (responseId == "") {
         return false;
      }
      if (responseId != null) {
         return true;
      }
      // An event is a request type event if the it's class has a [Response] tag that
      // specifies a request id that's the same as the type-id of this event.
      responseId = ReflectionUtil.getFilteredClassTagValue(
            event, "Response", "request", event.type, "name" );
      if (responseId != null) {
         _requestIdByResponseId[responseId] = event.type;
         _responseIdByRequestId[event.type] = responseId;
         return true;
      }
      responseId = ReflectionUtil.getFilteredClassTagValue(
            event, "Response", "request", undefined, "name" );
      if (responseId == null) {
         _responseIdByRequestId[event.type] = ""; // for quick lookup in the future
         return false;
      }
      // If there's a Reponse tag but it doesn't specify a request id, then we need to
      // additionally make sure that this event is not a response event (in the cases
      // where same event class serves as the response too). So in that case we check if
      // the event identifies itself with some request.
      if (ResponseInfo.carriesRequestInfo( event )) {
         // The event looks like a response rather than a request.
         _responseIdByRequestId[event.type] = ""; // for quick lookup in the future
         return false;
      }
      _requestIdByResponseId[responseId] = event.type;
      _responseIdByRequestId[event.type] = responseId;
      return true;
   }

   private function isResponseEvent( event:Event ):Boolean {
      var requestId:String = _requestIdByResponseId[event.type];
      if (StringUtils.isNullorEmpty( requestId )) {
         return false;
      }
      return true;
   }

   /**
    * Checks with the registered pre-dispatch callbacks to see whether the event should
    * be dispatched.
    */
   private function isValidDispatch( ev:Event ):Boolean {
      var predispatchFlagsByCallback:Dictionary =
            _predispatchCallbacks.forEach( invokePredispatchCallback, ev );
      for each (var flag:Boolean in predispatchFlagsByCallback) {
         if (flag == false) {
            return false;
         }
      }
      return true;
   }

   private function invokePredispatchCallback( callback:Function, event:Event ):Object {
      return callback( event );
   }

   private function onTypeAddedToDispatcher( ev:EventTypeUpdateEvent ):void {
      var participant:Object = ev.object;
      var eventTypeId:String = ev.eventTypeAddedOrRemoved;
      participant.addEventListener( eventTypeId, onEvent );
   }

   private function onTypeRemovedFromDispatcher( ev:EventTypeUpdateEvent ):void {
      var participant:Object = ev.object;
      var eventTypeId:String = ev.eventTypeAddedOrRemoved;
      participant.removeEventListener( eventTypeId, onEvent );
   }
} // class

}

/**
 * Information maintained by EventBus for an event messaging participant.
 */
class ParticipantInfo
{
   public var isParticipantWeakReferenced:Boolean = false;

   /**
    * The number of request events (dispatched by the participant that this info pertains
    * to) that are still awaiting a response.
    */
   public var pendingRequestCount:uint = 0;
}